/*
 * Copyright 2012 Nicholas Bilyk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package
{
    
    import cocos2d.Cocos2D;
    import cocos2d.Cocos2DGame;
    import cocos2d.CCAnimation;
    import cocos2d.CCAnimate;
    import cocos2d.CCArray;
    import cocos2d.CCLayer;
    import cocos2d.CCPoint;
    import cocos2d.CCScaledLayer;
    import cocos2d.CCSize;
    import cocos2d.CCSprite;
    import cocos2d.CCSpriteFrame;
    import cocos2d.CCSpriteFrameCache;
    import cocos2d.ScaleMode;
    import cocos2d.CCUserDefault;

    import Views.Block;
    import Views.Goal;
    import Views.Player;
    import Views.Hunter;
    import Views.Piece;
    import Util.ViewUtil;

    import Loom.GameFramework.LoomGame;
    import Loom.GameFramework.TimeManager;

    import CocosDenshion.SimpleAudioEngine;

    /**
     * PieGame is the core game class.
     */
    class PieGame extends Cocos2DGame
    {

        [Inject]
        public var timeManager:TimeManager = null;

        private static const INT_MAX:int = 999999;

        private static const WIDTH:Number = 400;
        private static const HEIGHT:Number = 350;

        private static const MARGIN_TOP:uint = 15;
        private static const MARGIN_LEFT:uint = 14;
        private static const SS:uint = 50;
        
        private var currentLevel:uint;
        private var player:Player;
        private var goal:Goal;
        private var board:Vector.<Vector.<Piece> >;
        private var hunters:Vector.<Hunter>;
        private var isPlaying:Boolean = false;
        private var showingCutscene:Boolean = false;

        private var grid:CCSprite;
        private var gridSize:CCSize;
        private var audioButton:CCSprite;
        private var _isMusicPlaying:Boolean = true;

        public function run():void 
        {
            // Pass control to root game.
            super.run();
            group.getInjector().apply(this);

            var audioEngine:SimpleAudioEngine = SimpleAudioEngine.sharedEngine();
            audioEngine.playBackgroundMusic("assets/piegameloop.mp3");

            // Set the current level to the last level the player was at.
            var userDefaults:CCUserDefault = CCUserDefault.sharedUserDefault();
            currentLevel = userDefaults.getIntegerForKey("currentLevel");

            // Create children
            var cache:CCSpriteFrameCache = CCSpriteFrameCache.sharedSpriteFrameCache();
            cache.addSpriteFramesWithFile("assets/PieUI.plist", "assets/PieUI.png");

            // game layer
            layer.scaleMode = ScaleMode.LETTERBOX;
            layer.designWidth = 550;
            layer.designHeight = 400;
            

            // add the background
            var bg:CCSprite = ViewUtil.createSprite("Background0000");
            bg.x = layer.designWidth * 0.5;
            bg.y = layer.designHeight * 0.5;
            layer.addChild(bg);

            grid = ViewUtil.createSprite("Grid0000");
            grid.setAnchorPoint(new CCPoint(0.0, 0.5));
            gridSize = grid.getContentSize();
            grid.x = layer.designWidth - gridSize.width - 10;
            grid.y = layer.designHeight * 0.5;

            layer.addChild(grid);

            grid.onTouchBegan += onGridTouchBegan;

            var resetButton:CCSprite = ViewUtil.createSprite("ResetButton0000");
            resetButton.x = (layer.designWidth - gridSize.width - 10) / 2;
            resetButton.y = layer.designHeight * 0.5;
            resetButton.onTouchBegan += function(touchId:int, x:Number, y:Number):void {
                if (showingCutscene) return;
                drawLevel();
            };
            
            layer.addChild(resetButton);

            audioButton = ViewUtil.createSprite("AudioButton0000");
            audioButton.x = (layer.designWidth - gridSize.width - 10) / 2;
            audioButton.y = layer.designHeight * 0.2;
            audioButton.onTouchBegan += function(touchId:int, x:Number, y:Number):void {
                if (_isMusicPlaying) {
                    _isMusicPlaying = false;
                    audioEngine.pauseBackgroundMusic();
                    ViewUtil.setFrameByName(audioButton, "AudioButton0001");
                } else {
                    _isMusicPlaying = true;
                    audioEngine.resumeBackgroundMusic();
                    ViewUtil.setFrameByName(audioButton, "AudioButton0000");
                }
            }
            layer.addChild(audioButton);
            
            begin();
        }

        
        /**
         * Initializes and starts the game.
         */ 
        private function begin():void {
            // Append victory level.
            Levels.levels.pushSingle( 
            [[4, 4, 4, 0, 0, 0, 0, 0],
             [4, 0, 0, 0, 0, 0, 0, 0],
             [4, 0, 0, 3, 0, 0, 0, 0],
             [4, 4, 0, 0, 0, 4, 4, 0],
             [4, 0, 0, 4, 0, 4, 0, 4],
             [4, 0, 0, 4, 0, 4, 0, 4],
             [4, 0, 0, 4, 0, 4, 0, 2]]);
            
        
            drawLevel();
        }

        private function drawLevel():void {
            Console.print("Drawing level " + currentLevel);

            isPlaying = true;
            grid.removeAllChildrenWithCleanup(true);
            
            
            var thisLevel:Vector.<Vector.<uint> > = Levels.levels[currentLevel];
            var rows:uint = thisLevel.length;
            board = new Vector.<Vector.<Piece> >(rows);
            hunters = [];
            var zOrder:uint;
            for (var i:uint = 0; i < rows; i++) {
                var row:Vector.<uint> = thisLevel[i];
                var cols:uint = row.length;
                board[i] = new Vector.<Piece>(cols);
                for (var j:uint = 0; j < cols; j++) {
                    var v:uint = row[j];
                    var newPiece:Piece = null;
                    if (v == PiecesEnum.BLOCK) {
                        newPiece = new Block();
                        zOrder = 1;
                    } else if (v == PiecesEnum.PLAYER) {
                        player = new Player();
                        newPiece = player;
                        zOrder = 2;
                    } else if (v == PiecesEnum.PIE) {
                        goal = new Goal();
                        newPiece = goal;
                        zOrder = 0;
                    } else if (v == PiecesEnum.HUNTER) {
                        var h1:Hunter = new Hunter();
                        newPiece = h1;
                        hunters.pushSingle(newPiece);
                        zOrder = 3;
                    }
                    if (newPiece) {
                        newPiece.row = i;
                        newPiece.col = j;
                        newPiece.view.setAnchorPoint(new CCPoint(0.0, 1.0));
                        var p:CCPoint = getPointFromLocation(i, j);
                        newPiece.view.x = p.x;
                        newPiece.view.y = p.y;
                        grid.addChild(newPiece.view, zOrder);
                        board[i][j] = newPiece;
                    } else {
                        board[i][j] =  null;
                    }
                }
            }
            
            // Ending cutscene
            if (currentLevel == Levels.levels.length - 1) {
                isPlaying = false;
                showingCutscene = true;
                var c:uint = 0;
                for each (var hunter:Hunter in hunters) {
                    setTimeout(hunter.deploySpikes, c * 10 + 2000);
                    setTimeout(hunter.move, 3000, player.view.x, player.view.y);
                    setTimeout(hunter.move, c * 70 + 6000, hunter.view.x, hunter.view.y);
                    c++;
                }
                setTimeout(grid.removeChild, 4000, player.view, true);
                setTimeout(function():void { showingCutscene = false; currentLevel = 0; }, 10000, player.view, true);
            }
            
        }


        //----------------------------------
        // Player movement
        //----------------------------------

        /**
         * Move player
         * @var xD - horizontal direction
         * @var yD - vertical direction
         */
        private function movePlayer(xDir:int, yDir:int):void {
            if (!withinBounds(player.row + yDir, player.col + xDir)) return;
            var row:Vector.<Piece> = board[player.row + yDir];
            
            var cellItem:Piece = row[player.col + xDir];
            if (cellItem is Goal) {
                isPlaying = false;
                currentLevel++;
                var userDefaults:CCUserDefault = CCUserDefault.sharedUserDefault();
                userDefaults.setIntegerForKey("currentLevel", currentLevel);
                setTimeout(player.victory, 200);
                setTimeout(drawLevel, 1500);
            } else if (cellItem is Block) {
                // Move the block if you player can.
                var block:Block = cellItem as Block;
                if (!block.movesLeft) return;
                var blockToRow:int = player.row + 2 * yDir;
                var blockToCol:int = player.col + 2 * xDir;
                if (!withinBounds(blockToRow, blockToCol) || board[blockToRow][blockToCol] != null) return;
                moveItem(player.row + yDir, player.col + xDir, blockToRow, blockToCol);
                block.movesLeft--;
            }
            moveItem(player.row, player.col, player.row + yDir, player.col + xDir);
            
            if (cellItem is Hunter) {
                lose(cellItem as Hunter);
                return;
            }
            
            if (!(cellItem is Goal)) {
                // Move hunters if player didn't just win.
                var playerDistance:Vector.<Vector.<uint> > = []; // Player distance vector
                calculateDistancePath(player.row, player.col, playerDistance);
                var pieDistance:Vector.<Vector.<uint> > = []; // Pie distance vector
                calculateDistancePath(goal.row, goal.col, pieDistance);
                for each (var hunter:Hunter in hunters) {
                    moveHunter(hunter, playerDistance, pieDistance);
                }
            }
        }
        
        /**
         * Moves an item on the board.
         */
        private function moveItem(fromRow:int, fromCol:int, toRow:int, toCol:int):void {
            var previousItem:Piece = board[fromRow][fromCol];
            board[fromRow][fromCol] = null;
            board[toRow][toCol] = previousItem;
            previousItem.row = toRow;
            previousItem.col = toCol;
            var p:CCPoint = getPointFromLocation(toRow, toCol);
            previousItem.move(p.x, p.y);
        }

        //----------------------------------
        // AI
        //----------------------------------

        
        /**
         * Calculate distance path (Single source, shortest path algorithm)
         */
        private function calculateDistancePath(row:int, col:int, arr:Vector.<Vector.<uint> >):void {
            if (arr.length == 0) {
                // Initialize solution vector
                var rows:uint = board.length;
                for (var i:uint = 0; i < rows; i++) {
                    var cols:uint = board[i].length;
                    arr.pushSingle(new Vector.<uint>(cols));
                    for (var j:uint = 0; j < cols; j++) {
                        arr[i][j] = INT_MAX;
                    }
                }
                arr[row][col] = 0;
            }
            
            var directions:Vector.<Vector.<uint> > = [[-1, 0], [0, 1], [1, 0], [0, -1]];
            var recurse:Vector.<Vector.<uint> > = []; // Which directions to recurse
            var dir:Vector.<uint>;
            var newRow:int;
            var newCol:int;
            var weight:int;
            for each (dir in directions) {
                newRow = row + dir[0]; // New row
                newCol = col + dir[1]; // New column
                if (!withinBounds(newRow, newCol)) continue;
                var value:Piece = board[newRow][newCol] as Piece;
                if (!value) weight = 1;
                else if (value is Block) continue;
                else if (value is Goal) continue;
                else if (value is Hunter) weight = 2;
                
                if (arr[row][col] + weight < arr[newRow][newCol]) {
                    arr[newRow][newCol] = arr[row][col] + weight;
                    recurse.pushSingle(dir);
                }
            }
            for each (dir in recurse) {
                newRow = row + dir[0]; // New row
                newCol = col + dir[1]; // New column
                calculateDistancePath(newRow, newCol, arr);
            }
        }
        
        /**
         * Move hunter towards prey
         */
        private function moveHunter(hunter:Hunter, playerDistance:Vector.<Vector.<uint> >, pieDistance:Vector.<Vector.<uint> >):void {
            // Up, Right, Down, Left
            var directionVec:Vector.<Vector.<int> > = [[0, 0], [-1, 0], [0, 1], [1, 0], [0, -1]];
            var minValue:int = INT_MAX;
            var n:uint = 0;
            for (var i:uint = 0; i < 5; i++) {
                var row:int = hunter.row + directionVec[i][0];
                var col:int = hunter.col + directionVec[i][1];
                if (!withinBounds(row, col)) continue;
                var cellItem:Piece = board[row][col] as Piece;
                if (cellItem is Hunter) continue;
                var value:int = playerDistance[row][col];
                
                if (value != -1 && value <= minValue) {
                    if (value == minValue) {
                        // Consider the distance to the pie when faced with two equal options
                        var pR:int = hunter.row + directionVec[n][0];
                        var pC:int = hunter.col + directionVec[n][1];
                        if (pieDistance[row][col] > pieDistance[pR][pC]) continue; 
                    }
                    n = i; minValue = value;
                }
            }
            var newRow:uint = hunter.row + directionVec[n][0];
            var newCol:uint = hunter.col + directionVec[n][1];
            if (minValue == INT_MAX) return;
            
            moveItem(hunter.row, hunter.col, newRow, newCol);
            if (minValue < 4) hunter.deploySpikes();
            
            if (hunter.row == player.row && hunter.col == player.col) lose(hunter);
        }
        
        /**
         *  Check if square is in bounds.
         */
        private function withinBounds(row:int, col:int):Boolean {
            if (row < 0) return false; // Top wall
            if (col < 0) return false; // Left wall
            if (row >= board.length) return false; // Bottom wall
            var levelRow:Vector.<uint> = board[row];
            if (col >= levelRow.length) return false; // Right wall
            return true;
        }

        /**
         * Lose
         */
        private function lose(hunter:Hunter):void {
            isPlaying = false;
            setTimeout(hunter.kill, 200);
            setTimeout(drawLevel, 1200);
        }

        //----------------------------------
        // USER INPUT
        //----------------------------------

        private function onGridTouchBegan(touchId:int, x:Number, y:Number):void {
            if (!isPlaying) return;
            var p:CCPoint = new CCPoint(x, y);

            var location:CCPoint = getLocationFromPoint(p.x, p.y);
            
            if (player.col != location.x || player.row != location.y) {
                if (Math.abs(location.x - player.col) > Math.abs(location.y - player.row)) {
                    movePlayer(Math.clamp(location.x - player.col, -1, 1), 0);
                } else {
                    movePlayer(0, Math.clamp(location.y - player.row, -1, 1));
                }
                
            }
        }


        //----------------------------------
        // UTILITY METHODS
        //----------------------------------

        private function setTimeout(f:Function, delay:Number, ... arguments) {
            timeManager.schedule(delay, null, function():void {
                f.apply(null, arguments);
            });
        }

        private function displayText(text:String):void {
            Console.print(text);
        }

        private function getPointFromLocation(row:uint, col:uint):CCPoint {
            return new CCPoint(col * SS + MARGIN_LEFT, gridSize.height - row * SS - MARGIN_TOP);
        }

        private function getLocationFromPoint(x:Number, y:Number):CCPoint {
            return new CCPoint(Math.floor((x - MARGIN_LEFT) / SS), Math.floor(-(y + MARGIN_TOP - gridSize.height) /  SS));
        }
       
    }
}